package com.nowcoder.community.service;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nowcoder.community.entity.DiscussPost;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class ElasticsearchService {
    @Autowired
    private ElasticsearchRepository elasticsearchRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    public void addDiscussPost(DiscussPost post){
        elasticsearchRepository.save(post);
    }

    public void deleteDiscussPost(int id){
        elasticsearchRepository.deleteById(id);
    }

    public AggregatedPage<DiscussPost> searchDiscussPost(String keyword, int page, int size){
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
                .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                .withHighlightFields(
                        new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
                        new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
                ).withPageable(PageRequest.of(page, size))
                .build();

        return elasticsearchTemplate.queryForPage(searchQuery, DiscussPost.class, new Mapper());

    }

    private class Mapper implements SearchResultMapper {
        private ObjectMapper objectMapper;

        public Mapper() {
            this.objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        }

        @Override
        public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
            List<DiscussPost> list = new ArrayList<>();
            for (SearchHit hit: response.getHits()){
                DiscussPost post = mapSearchHit(hit, DiscussPost.class);

                // 处理高亮结果
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if (highlightFields.get("title") != null){
                    post.setTitle(highlightFields.get("title").getFragments()[0].toString());
                }
                if (highlightFields.get("content") != null){
                    post.setContent(highlightFields.get("content").getFragments()[0].toString());
                }
                list.add(post);
            }
            long totalHits = response.getHits().getTotalHits();
            float maxScore = response.getHits().getMaxScore();
            return new AggregatedPageImpl(list, pageable, totalHits, response.getAggregations(), response.getScrollId(),
                    maxScore);
        }

        @Override
        public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
            return objectMapper.convertValue(searchHit.getSourceAsMap(), type);
        }
    }
}
